package com.tencent.sr.iris.activity.service.activity.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.tencent.sr.iris.activity.common.enums.EventHandingRecordStatusEnum;
import com.tencent.sr.iris.activity.common.util.ShardingIdUtil;
import com.tencent.sr.iris.activity.domain.event.dto.ActivityDTO;
import com.tencent.sr.iris.activity.domain.event.dto.ActivityTaskDTO;
import com.tencent.sr.iris.activity.interfaces.event.dto.ActivityEventDTO;
import com.tencent.sr.iris.activity.interfaces.request.acticity.QueryEventHandingRecordPageRequest;
import com.tencent.sr.iris.activity.interfaces.request.task.EventRetryRequest;
import com.tencent.sr.iris.activity.interfaces.response.activity.EventHandingRecordResponse;
import com.tencent.sr.iris.activity.repository.Criteria;
import com.tencent.sr.iris.activity.repository.entity.TIrisEventHandingRecordDO;
import com.tencent.sr.iris.activity.repository.mapper.TIrisEventHandingRecordMapper;
import com.tencent.sr.iris.activity.repository.mapper.example.TIrisEventHandingRecordExample;
import com.tencent.sr.iris.activity.service.activity.IrisEventHandingRecordService;
import com.tencent.sr.iris.activity.service.event.vo.IrisEventHandleContext;
import com.tencent.sr.iris.activity.service.util.IdCreateorUtil;
import com.tencent.sr.rmall.common.exception.retail.TRetailBizException;
import com.tencent.sr.rmall.common.request.PaginationRequest;
import com.tencent.sr.rmall.common.response.PaginationResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.skywalking.apm.toolkit.trace.TraceContext;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Service
public class IrisEventHandingRecordServiceImpl implements IrisEventHandingRecordService {

    @Resource
    private TIrisEventHandingRecordMapper eventHandingRecordMapper;
    @Resource
    IdCreateorUtil idCreateorUtil;

    @Override
    public TIrisEventHandingRecordDO queryByBizId(int shardingId, String bizId) {
        TIrisEventHandingRecordExample example = new TIrisEventHandingRecordExample();
        example.createCriteria()
                .andPropertyEqualTo(TIrisEventHandingRecordExample.SHARDING_ID, shardingId)
                .andPropertyEqualTo(TIrisEventHandingRecordExample.BIZ_ID, bizId);
        List<TIrisEventHandingRecordDO> list = eventHandingRecordMapper.selectByExample(example);
        if (CollectionUtil.isEmpty(list)) {
            return null;
        }
        return list.get(0);
    }

    @Override
    public boolean saveRecord(TIrisEventHandingRecordDO eventHandingRecordDO) {
        int i = eventHandingRecordMapper.insertSelective(eventHandingRecordDO);
        return i == 1;
    }

    @Override
    public boolean updateRecord(TIrisEventHandingRecordDO eventHandingRecordDO) {

        int i = 0;
        try {
            TIrisEventHandingRecordExample example = new TIrisEventHandingRecordExample();
            example.createCriteria()
                    .andPropertyEqualTo(TIrisEventHandingRecordExample.BIZ_ID, eventHandingRecordDO.getBizId())
                    .andPropertyEqualTo(TIrisEventHandingRecordExample.SHARDING_ID,
                            eventHandingRecordDO.getShardingId());
            //shardingId有值会导致报错
            eventHandingRecordDO.setShardingId(null);
            eventHandingRecordDO.setId(null);
            eventHandingRecordDO.setUpdateTime(new Date());
            i = eventHandingRecordMapper.updateByExampleSelective(eventHandingRecordDO, example);
        } catch (Exception e) {
            log.error("更新事件流水异常，request:{}", JSON.toJSONString(eventHandingRecordDO), e);
        }
        return i == 1;
    }

    /**
     * 记录事件级别处理日志
     *
     * @param eventDTO
     */
    @Override
    public TIrisEventHandingRecordDO saveHandingRecord(ActivityEventDTO eventDTO, ActivityDTO activityDTO,
            ActivityTaskDTO activityTaskDTO, List<String> handingInfoList) {
        String bizId = idCreateorUtil.genId();
        TIrisEventHandingRecordDO handingRecordDO = new TIrisEventHandingRecordDO();
        handingRecordDO.setBizId(bizId);
        handingRecordDO.setUid(eventDTO.getUid());
        handingRecordDO.setPid(eventDTO.getPid());
        handingRecordDO.setEventTime(eventDTO.getEventTime() == null ? null : new Date(eventDTO.getEventTime()));
        handingRecordDO.setEventInfo(JSON.toJSONString(eventDTO));
        handingRecordDO.setEventType(eventDTO.getEventType());
        handingRecordDO.setFailCount((byte) 0);
        handingRecordDO.setIdempotent(eventDTO.getIdempotent());
        String actBizId = activityDTO == null ? null : activityDTO.getBizId();
        handingRecordDO.setActivityId(actBizId);
        String taskBizId = activityTaskDTO == null ? null : activityTaskDTO.getBizId();
        handingRecordDO.setTaskId(taskBizId);
        //防止字段超长，db最大长度 4096
        String handingInfo = org.apache.commons.lang3.StringUtils
                .substring(JSON.toJSONString(handingInfoList), 0, 4000);
        //加上traceId，便于排查问题
        handingInfo = "traceId:" + TraceContext.traceId() + ";" + handingInfo;
        handingRecordDO.setHandingInfo(handingInfo);
        handingRecordDO.setHandingStatus(EventHandingRecordStatusEnum.HANDLING.getCode());
        handingRecordDO.setShardingId(ShardingIdUtil.sharding(eventDTO.getUid()));
        saveRecord(handingRecordDO);
        return handingRecordDO;
    }

    @Override
    public TIrisEventHandingRecordDO queryRecord(ActivityEventDTO eventDTO, ActivityDTO activityDTO,
            ActivityTaskDTO activityTaskDTO) {
        if (eventDTO == null) {
            throw new TRetailBizException("查询事件处理日志，事件不能为空");
        }
        if (StringUtils.isEmpty(eventDTO.getIdempotent())) {
            throw new TRetailBizException("查询事件处理日志，事件幂等id不能为空");
        }
        if (StringUtils.isEmpty(eventDTO.getUid())) {
            throw new TRetailBizException("查询事件处理日志，事件uid不能为空");
        }
        String uid = eventDTO.getUid();
        String eventIdempotent = eventDTO.getIdempotent();
        String activityId = activityDTO == null ? "" : activityDTO.getBizId();
        String taskId = activityTaskDTO == null ? "" : activityTaskDTO.getBizId();
        int shardingId = ShardingIdUtil.doSharding(uid);

        TIrisEventHandingRecordExample example = new TIrisEventHandingRecordExample();
        Criteria criteria = example.createCriteria();
        criteria.andPropertyEqualTo(TIrisEventHandingRecordExample.SHARDING_ID, shardingId);
        criteria.andPropertyEqualTo(TIrisEventHandingRecordExample.IDEMPOTENT, eventIdempotent);
        criteria.andPropertyEqualTo(TIrisEventHandingRecordExample.ACTIVITY_ID, activityId);
        criteria.andPropertyEqualTo(TIrisEventHandingRecordExample.TASK_ID, taskId);

        List<TIrisEventHandingRecordDO> list = eventHandingRecordMapper.selectByExample(example);
        if (CollectionUtil.isEmpty(list)) {
            return null;
        }
        if (list.size() > 1) {
            throw new TRetailBizException(
                    String.format("根据参数查询到多条记录eventIdempotent:%s,activityId:%s,taskId:%s", eventIdempotent, activityId,
                            taskId));
        }
        return list.get(0);
    }

    @Override
    public TIrisEventHandingRecordDO queryRecord(IrisEventHandleContext context) {
        ActivityEventDTO eventDTO = context.getEventDTO();
        if (eventDTO == null) {
            throw new TRetailBizException("eventDTO不能为空");
        }
        return queryRecord(context.getEventDTO(), context.getActivityDTO(), context.getActivityTaskDTO());
    }

    @Override
    public TIrisEventHandingRecordDO saveHandingRecord(IrisEventHandleContext context, List<String> handingInfoList) {
        ActivityEventDTO eventDTO = context.getEventDTO();
        if (eventDTO == null) {
            throw new TRetailBizException("eventDTO不能为空");
        }
        String idempotent = eventDTO.getIdempotent();
        String actBizId = context.getActivityDTO() == null ? null : context.getActivityDTO().getBizId();
        String taskBizId = context.getActivityTaskDTO() == null ? null : context.getActivityTaskDTO().getBizId();

        TIrisEventHandingRecordDO record = new TIrisEventHandingRecordDO();
        TIrisEventHandingRecordDO handingRecordDO = new TIrisEventHandingRecordDO();
        handingRecordDO.setBizId(idCreateorUtil.genId());
        handingRecordDO.setUid(eventDTO.getUid());
        handingRecordDO.setPid(eventDTO.getPid());
        handingRecordDO.setEventInfo(eventDTO.getEventInfo());
        handingRecordDO.setEventType(eventDTO.getEventType());
        handingRecordDO.setHandingStatus(EventHandingRecordStatusEnum.SUCCEES.getCode());
        handingRecordDO.setFailCount((byte) 0);
        handingRecordDO.setIdempotent(idempotent);
        handingRecordDO.setActivityId(actBizId);
        handingRecordDO.setTaskId(taskBizId);
        handingRecordDO.setHandingInfo(JSON.toJSONString(handingInfoList));
        handingRecordDO.setHandingStatus(EventHandingRecordStatusEnum.HANDLING.getCode());
        handingRecordDO.setShardingId(ShardingIdUtil.sharding(eventDTO.getUid()));
        saveRecord(record);
        return record;
    }

    @Override
    public List<TIrisEventHandingRecordDO> queryHandingRecords(int shardingId, int offset, int limit,
            EventRetryRequest request) {
        List<TIrisEventHandingRecordDO> handingRecordDOList;
        TIrisEventHandingRecordExample example = new TIrisEventHandingRecordExample();

        Criteria criteria = example.createCriteria();
        criteria.andPropertyEqualTo(TIrisEventHandingRecordExample.SHARDING_ID, shardingId);

        String startTime = request.getStartTime();
        String endTime = request.getEndTime();
        if (StrUtil.isNotBlank(startTime)) {
            criteria.andPropertyGreaterThanOrEqualTo(TIrisEventHandingRecordExample.EVENT_TIME, startTime);
        }
        if (StrUtil.isNotBlank(endTime)) {
            criteria.andPropertyLessThanOrEqualTo(TIrisEventHandingRecordExample.EVENT_TIME, endTime);
        }
        if (!Strings.isNullOrEmpty(request.getEventType())) {
            criteria.andPropertyEqualTo(TIrisEventHandingRecordExample.EVENT_TYPE, request.getEventType());
        }
        if (Strings.isNullOrEmpty(request.getActivityId())) {
            criteria.andPropertyEqualTo(TIrisEventHandingRecordExample.ACTIVITY_ID, "");
        } else {
            criteria.andPropertyEqualTo(TIrisEventHandingRecordExample.ACTIVITY_ID, request.getActivityId());
        }
        if (Strings.isNullOrEmpty(request.getTaskId())) {
            criteria.andPropertyEqualTo(TIrisEventHandingRecordExample.TASK_ID, "");
        } else {
            criteria.andPropertyEqualTo(TIrisEventHandingRecordExample.TASK_ID, request.getTaskId());
        }
        if (CollectionUtil.isNotEmpty(request.getUidList())) {
            criteria.andPropertyIn(TIrisEventHandingRecordExample.UID, request.getUidList());
        }
        if (CollectionUtil.isNotEmpty(request.getEventIdempotentList())) {
            criteria.andPropertyIn(TIrisEventHandingRecordExample.IDEMPOTENT, request.getEventIdempotentList());
        }
        if (request.getHandingStatus() != null) {
            criteria.andPropertyEqualTo(TIrisEventHandingRecordExample.HANDING_STATUS, request.getHandingStatus());
        }
        if (!Strings.isNullOrEmpty(request.getHandingInfo())) {
            criteria.andPropertyEqualTo(TIrisEventHandingRecordExample.HANDING_INFO, request.getHandingInfo());
        }
        if (!Strings.isNullOrEmpty(request.getEventInfo())) {
            criteria.andPropertyLike(TIrisEventHandingRecordExample.EVENT_INFO, request.getEventInfo());
        }
        handingRecordDOList = eventHandingRecordMapper.limitSelectByExample(
                example, offset, limit);
        return handingRecordDOList;
    }

    @Override
    public TIrisEventHandingRecordDO queryOneHandingRecord(int shardingId, String pid, String uid, String taskBizId) {
        TIrisEventHandingRecordExample example = new TIrisEventHandingRecordExample();
        Criteria criteria = example.createCriteria();
        criteria.andPropertyEqualTo(TIrisEventHandingRecordExample.SHARDING_ID, shardingId);
        criteria.andPropertyEqualTo(TIrisEventHandingRecordExample.TASK_ID, taskBizId);
        if (StrUtil.isNotBlank(pid)) {
            criteria.andPropertyEqualTo(TIrisEventHandingRecordExample.PID, pid);
        }
        if (StrUtil.isNotBlank(uid)) {
            criteria.andPropertyEqualTo(TIrisEventHandingRecordExample.UID, uid);
        }

        List<TIrisEventHandingRecordDO> handingRecordDOList = eventHandingRecordMapper.limitSelectByExample(example, 0, 1);
        if (CollectionUtil.isEmpty(handingRecordDOList)) {
            return null;
        }

        return handingRecordDOList.get(0);
    }


    @Override
    public List<TIrisEventHandingRecordDO> queryNeedRetryRecords(int shardingId, int offset, int limit) {
        List<TIrisEventHandingRecordDO> failEventRecords;
        TIrisEventHandingRecordExample example = new TIrisEventHandingRecordExample();
        //查询创建时间3个月内，且失败次数<3的的流水
        Calendar calendar1 = Calendar.getInstance();
        calendar1.add(Calendar.MONTH, -3);
        Date createTime = calendar1.getTime();
        //执行失败的流水
        Criteria criteria = example.createCriteria();
        criteria.andPropertyEqualTo(TIrisEventHandingRecordExample.SHARDING_ID, shardingId)
                .andPropertyEqualTo(TIrisEventHandingRecordExample.HANDING_STATUS,
                        EventHandingRecordStatusEnum.FAIL.getCode())
                .andPropertyLessThan(TIrisEventHandingRecordExample.FAIL_COUNT, 3)
                .andPropertyGreaterThan(TIrisEventHandingRecordExample.CREATE_TIME, createTime);
        //获取30分钟前的时间点
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.MINUTE, -30);
        Date maxTime = calendar.getTime();
        //或查询三个月内，执行时间超过30分钟，还在处理中的记录
        Criteria orCriteria = example.or();
        orCriteria.andPropertyEqualTo(TIrisEventHandingRecordExample.SHARDING_ID, shardingId)
                .andPropertyEqualTo(TIrisEventHandingRecordExample.HANDING_STATUS,
                        EventHandingRecordStatusEnum.HANDLING.getCode())
                .andPropertyLessThan(TIrisEventHandingRecordExample.UPDATE_TIME, maxTime)
                .andPropertyLessThan(TIrisEventHandingRecordExample.FAIL_COUNT, 3)
                .andPropertyGreaterThan(TIrisEventHandingRecordExample.CREATE_TIME, createTime);

        failEventRecords = eventHandingRecordMapper.limitSelectByExample(
                example, offset, limit);
        return failEventRecords;
    }

    @Override
    public PaginationResponse<EventHandingRecordResponse> queryEventHandingInfoPage(
            PaginationRequest<QueryEventHandingRecordPageRequest> request) {
        PaginationResponse<EventHandingRecordResponse> response = new PaginationResponse<>();
        String uid = request.getQueryCriteria().getUid();
        int shardingId = ShardingIdUtil.doSharding(uid);

        TIrisEventHandingRecordExample example = new TIrisEventHandingRecordExample();

        Criteria criteria = example.createCriteria();
        criteria.andPropertyEqualTo(TIrisEventHandingRecordExample.SHARDING_ID, shardingId)
                .andPropertyGreaterThanOrEqualTo(TIrisEventHandingRecordExample.EVENT_TIME, request.getQueryCriteria().getStartTime())
                .andPropertyLessThanOrEqualTo(TIrisEventHandingRecordExample.EVENT_TIME, request.getQueryCriteria().getEndTime());
        if (!Strings.isNullOrEmpty(request.getQueryCriteria().getEventType())) {
            criteria.andPropertyEqualTo(TIrisEventHandingRecordExample.EVENT_TYPE, request.getQueryCriteria().getEventType());
        }
        if (Strings.isNullOrEmpty(request.getQueryCriteria().getActivityId())) {
            criteria.andPropertyEqualTo(TIrisEventHandingRecordExample.ACTIVITY_ID, "");
        } else {
            criteria.andPropertyEqualTo(TIrisEventHandingRecordExample.ACTIVITY_ID, request.getQueryCriteria().getActivityId());
        }
        if (Strings.isNullOrEmpty(request.getQueryCriteria().getTaskId())) {
            criteria.andPropertyEqualTo(TIrisEventHandingRecordExample.TASK_ID, "");
        } else {
            criteria.andPropertyEqualTo(TIrisEventHandingRecordExample.TASK_ID, request.getQueryCriteria().getTaskId());
        }
        if (Strings.isNullOrEmpty(request.getQueryCriteria().getUid())) {
            criteria.andPropertyEqualTo(TIrisEventHandingRecordExample.UID, request.getQueryCriteria().getUid());
        }
        if (CollectionUtil.isNotEmpty(request.getQueryCriteria().getEventIdempotentList())) {
            criteria.andPropertyIn(TIrisEventHandingRecordExample.IDEMPOTENT, request.getQueryCriteria().getEventIdempotentList());
        }
        if (request.getQueryCriteria().getHandingStatus() != null) {
            criteria.andPropertyEqualTo(TIrisEventHandingRecordExample.HANDING_STATUS, request.getQueryCriteria().getHandingStatus());
        }
        if (!Strings.isNullOrEmpty(request.getQueryCriteria().getHandingInfo())) {
            criteria.andPropertyEqualTo(TIrisEventHandingRecordExample.HANDING_INFO, request.getQueryCriteria().getHandingInfo());
        }
        if (!Strings.isNullOrEmpty(request.getQueryCriteria().getEventInfo())) {
            criteria.andPropertyLike(TIrisEventHandingRecordExample.EVENT_INFO, request.getQueryCriteria().getEventInfo());
        }
        int offset = (request.getPageNum() - 1) * request.getPageSize();
        int limit = request.getPageSize();
        List<TIrisEventHandingRecordDO> handingRecordDOList = eventHandingRecordMapper.limitSelectByExample(
                example, offset, limit);
        List<EventHandingRecordResponse> dataList = Lists.newArrayList();
        if(CollectionUtil.isNotEmpty(handingRecordDOList)){
            dataList = handingRecordDOList.stream().map(n -> {
                EventHandingRecordResponse res = new EventHandingRecordResponse();
                BeanUtils.copyProperties(n, res);
                return res;
            }).collect(Collectors.toList());
        }
        response.setDataList(dataList);
        response.setPageSize(request.getPageSize());
        response.setPageNum(request.getPageNum());
        return response;
    }
}
